#=======================================================================
The game 2048 is a video game originally authored by Gabriele Cirulli.
See https://en.wikipedia.org/wiki/2048_(video_game) or, on Youtube,
https://gaming.youtube.com/game/UCkIzbSwpjk5eRRNaZPxTy2w
This example includes use of the Toolbar and Grid features of Gtk.jl.
========================================================================#

using Gtk.ShortNames

@enum Direction2048 Right Left Up Down

"""
    shifttiles!
The adding and condensing code is for a leftward shift, so if the move is not
leftward, this will rotate matrix to make move leftward, move, then undo rotation.
"""
function shifttiles!(b, siz, direction)
    if direction == Right
        tmpb = rot180(b); points, winner = leftshift!(tmpb, siz); tmpb = rot180(tmpb)
    elseif direction == Up
        tmpb = rotl90(b); points, winner = leftshift!(tmpb, siz); tmpb = rotr90(tmpb)
    elseif direction == Down
        tmpb = rotr90(b); points, winner = leftshift!(tmpb, siz); tmpb = rotl90(tmpb)
    else # left movement function as coded
        return leftshift!(b, siz) # b already modified so skip copy step below
    end
    for i in 1:siz, j in 1:siz
        b[i,j] = tmpb[i,j]   # copy tmpb contents back to b (modifies b)
    end
    points, winner
end


"""
    leftshift!
Work row by row. First, compact tiles to the left if possible. Second, find and
replace paired tiles in the row, then re-compact. Keep score of merges and return
as pointsgained. If a 2048 value tile is created, return a winner true value.
"""
function leftshift!(b, siz)
    pointsgained = 0
    winner = false
    for i in 1:siz
        tmprow = zeros(Int, siz)
        tmppos = 1
        for j in 1:siz
            if b[i,j] != 0
                tmprow[tmppos] = b[i,j]
                tmppos += 1
            end
        end
        b[i,:] = tmprow
        tmprow = zeros(Int, siz)
        tmppos = 1
        for j in 1:siz
            if j < siz && b[i,j] == b[i,j+1]
                b[i,j] = 2 * b[i,j]
                b[i,j+1] = 0
                pointsgained += b[i,j]
                if b[i,j] == 2048     # made a 2048 tile, which wins game
                    winner = true
                end
            end
            if b[i,j] != 0
                tmprow[tmppos] = b[i,j]
                tmppos += 1
            end
        end
        b[i,:] = tmprow
    end
    pointsgained, winner
end

"""
    app2048
Run game app, with boardsize (choose 4 for original game) as an argument.
"""
function app2048(bsize)
    win = Window("2048 Game", 400, 400) |> (Frame() |> (box = Box(:v)))
    toolbar = Toolbar()
    newgame = ToolButton("New Game")
    setproperty!(newgame, :label, "New Game")
    setproperty!(newgame, :is_important, true)
    undomove = ToolButton("Undo Move")
    setproperty!(undomove, :label, "Undo Move")
    setproperty!(undomove, :is_important, true)
    map(w->push!(toolbar,w),[newgame,undomove])
    grid = Grid()
    map(w -> push!(box, w),[toolbar, grid])
    buttons = Array{Gtk.GtkButtonLeaf,2}(bsize, bsize)
    for i in 1:bsize, j in 1:bsize
        grid[i,j] = buttons[i,j] = Button()
        setproperty!(buttons[i,j], :expand, true)
    end
    board = zeros(Int, (bsize,bsize))
    pastboardstates = []
    score = 0
    gameover = false
    condition = Condition()
    won = ""

    function update!()
        for i in 1:bsize, j in 1:bsize
            label = (board[i,j] > 0) ? board[i,j]: " "
            setproperty!(buttons[i,j], :label, label)
        end
        setproperty!(win, :title, "$won 2048 Game  (Score: $score)")
    end
    function newrandomtile!()
        blanks = Array{Tuple{Int,Int},1}()
        for i in 1:bsize, j in 1:bsize
            if board[i,j] == 0
                push!(blanks, (i,j))
            end
        end
        if length(blanks) == 0
            gameover = true
        else
            i,j = rand(blanks)
            board[i,j] = (rand() > 0.8) ? 4 : 2
        end
    end
    function initialize!(w)
        won = ""
        gameover = false
        for i in 1:bsize, j in 1:bsize
            board[i,j] = 0
            setproperty!(buttons[i,j], :label, " ")
        end
        newrandomtile!()
        update!()
    end
    function undo!(w)
        if gameover == false && length(pastboardstates) > 0
            board = pop!(pastboardstates)
            update!()
        end
    end
    function keypress(w, event)
        presses = Dict(0xff51 => Up,      # code rotated 90 degrees
                       0xff52 => Left,    # because of Gtk coordinates
                       0xff53 => Down,  
                       0xff54 => Right)
        keycode = event.keyval
        if haskey(presses, keycode) && gameover == false
            push!(pastboardstates, copy(board))
            newpoints, havewon = shifttiles!(board, bsize, presses[keycode])
            score += newpoints
            if havewon && won != "Winning"
                won = "Winning"
                info_dialog("You have won the game.")
            end
            newrandomtile!()
            update!()
            if gameover
                info_dialog("Game over.\nScore: $score")
            end
        end
    end
    endit(w) = notify(condition)
    initialize!(win)
    signal_connect(initialize!, newgame, :clicked)
    signal_connect(undo!,undomove, :clicked)
    signal_connect(endit, win, :destroy)
    signal_connect(keypress, win, "key-press-event")
    showall(win)
    wait(condition)
end


const boardsize = 4
app2048(boardsize)

